package terra.model.object.stat;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.ScheduledFuture;

import terra.managers.ThreadPoolManager;
import terra.model.Experience;
import terra.model.Stats;
import terra.model.object.LiveObject;

/**
 * Класс, описывающий базовые характеристики персонажа, как нпц, так и игроков
 * базовые характеристики : сила, ловкость, выносливость сила влияет на: урон
 * оружием ближнего боя, переносимый вес ловкость влияет на: скорость атаки,
 * скорость движения, высоту прыжка, урон дальнего боя. выносливость влияет на:
 * количество здоровья, на прибавку хп за уровень, на регенерацию хп, за запас
 * дыхания
 * 
 * хп = con*10 + (сon*con)/10*level, где (сon*con)/10*level - прибавка хп за
 * уровень. регенерация хп = 0.5 базовая + con/20*level; прибавка за уровень.
 * регенерация вызывается каждую 1 сек, если персонаж ранен. запас дыхания = 30
 * + con/5; 30 секунд + 1 секунда за каждые 5 единиц выносливости. сила атаки
 * ближний бой = сила_атаки_оружия + (сила_атаки_оружия/100*0.1)*str; оружие +
 * 0.1% за каждую единицу силы сила атаки дальний бой = сила_атаки_оружия +
 * (сила_атаки_оружия/100*0.1)*dex; оружие + 0.1% за каждую единицу ловкости
 * переносимый вес = 200 базовый + str*5; базовый вес + 5 единиц веса за 1
 * единицу силы скорость атаки = скорость атаки оружия + (скорость атаки
 * оружия/100*0.1)*dex; скорость атаки + 0.1% за каждую единицу ловкости
 * скорость движения = базовая скорость движения + ( базовая скорость
 * движения/100*0.05)*dex; базовая скорость + 0.05% за 1 единицу ловкости
 * 
 * шанс критического попадания = шанс_крита_типа_оружия + dex/10 , но не больше
 * 25%. критический урон = х2 + 0.5*dex/20 , но не больше х5. Прибавляет по 0.5
 * за каждые 20 ловкости
 * 
 * TODO ?!трогать ли высоту прыжка?! TODO урон от падения.
 * 
 * регенерацию следует выносить в отдельный поток. При достижении максимального
 * хп - отключаем тред. скорость движения зависит от того, где передвигается
 * персонаж - по земле, на лестнице, под водой или над водой.
 * */

public class CharacterStat implements Serializable{

	private static final long serialVersionUID = 1L;
	private short str = 10;
	private short dex = 10;
	private short con = 10;
	private int baseHp = 0;
	private float baseAttackRate = 1;
	private float baseAttack = 10;
	private float baseDefense = 1;
	private float baseSpeed = 64f;
	private float baseCrit = 1.5f;

	private int currentHp = 10;
	private int currentBreath = 30;
	private float attackPower = 1;
	private float attackRate = 1;
	private float defenseRate = 1;
	private float moveSpeed = 1f;
	private float criticalChance = 0.01f;
	private float criticalPower = 1.5f;
	private int maxBreath = 30;
	private int weight = 200;
	private int maxHp = 10;
	private float regenHp = 0.5f;
	@SuppressWarnings("unchecked")
	private ArrayList<Modifier>[] statModifiers = new ArrayList[Stats.NUM_STATS];
	private long exp = 0;
	private byte level = 1;
	private LiveObject owner;
	private transient ScheduledFuture<?> regenerationScheduledTask;

	class RegenerationTask implements Runnable {
		float count;

		@Override
		public void run() {
			count += getRegenHp();
			if (count >= 1) {
				setCurrentHp((int) (getCurrentHp() + (count - count % 1)));
				count -= (count - count % 1);
			}
		}
	}

	public CharacterStat(LiveObject liveObject) {
		owner = liveObject;
		calcStats();
		setCurrentHp(getMaxHp());
	}

	public boolean reduceHp(int count) {
		if (getCurrentHp() - count > 0) {
			setCurrentHp(getCurrentHp() - count);
			startRegenerationTask();
			return true;
		}
		return false;
	}

	private synchronized void startRegenerationTask() {
		if (regenerationScheduledTask != null)
			return;
		regenerationScheduledTask = ThreadPoolManager.getInstance()
				.scheduleGeneralAtFixedRate(new RegenerationTask(), 1000L,
						1000L);
	}

	protected synchronized void stopRegenerationTask() {
		if (regenerationScheduledTask == null)
			return;
		regenerationScheduledTask.cancel(true);
		regenerationScheduledTask = null;
	}

	public void calcStats() {
		/**
		 * шанс критического попадания = шанс_крита_типа_оружия + dex/10 , но не
		 * больше 25%. for(Stats stat :Stats.values()) { calcStat(stat);
		 * 
		 * }
		 */
		// * скорость атаки = скорость атаки оружия + (скорость атаки
		// оружия/100*0.1)*dex; скорость атаки + 0.1% за каждую единицу ловкости
		setAttackRate(getBaseAttackRate() + (getBaseAttackRate() / 100f * 0.1f)
				* getDex());
		// защита = одетая броня + CON /10;
		setDefenseRate(getBaseDefense() + getCon() / 10);
		// * скорость движения = базовая скорость движения + ( базовая скорость
		// движения/100*0.05)*dex; базовая скорость + 0.05% за 1 единицу
		// ловкости
		setMoveSpeed(getBaseSpeed() + (getBaseSpeed() / 100f * 0.05f)
				* getDex());
		// * сила атаки ближний бой = сила_атаки_оружия +
		// (сила_атаки_оружия/100*0.1)*str; оружие + 0.1% за каждую единицу силы
		setAttackPower(getBaseAttack() + (getBaseAttack() / 100f * 0.1f)
				* getStr());
		// 30 секунд + 1 секунда за каждые 5 единиц выносливости.
		setMaxBreath(30 + getCon() / 5);
		// 200 единиц веса + 5 единиц веса за 1 единицу силы
		setWeight(200 + getStr() * 5);
		// con*10 + (сon*con)/10*level, где (сon*con)/10*level - прибавка хп за
		// уровень.
		setMaxHp(getBaseHp() + getCon() * 10 + (getCon() * getCon()) / 10
				* getLevel());
		// регенерация хп = 0.5 базовая + con/20*level; прибавка за уровень.
		setRegenHp(0.1f + getCon() / 40f * getLevel());
		// х2 + 0.5*dex/20 , но не больше х5. Прибавляет по 0.5 за каждые 20
		// ловкости
		float critPower = (baseCrit + 0.5f * ((dex / 20)));
		setCriticalPower(critPower < 5 ? critPower : 5);
		// float baseCrit = this.baseCrit;
		// if (owner instanceof Player)
		// baseCrit = ((Player) owner).getWeapon().critRate;
		// float critPower = (baseCrit + 0.5f*((dex/20)));
		// setCriticalPower(critPower < 5 ? critPower : 5);

		if (getCurrentHp() < getMaxHp())
			startRegenerationTask();
	}

	public byte getLevel() {
		return level;
	}

	public void setLevel(byte _level) {
		this.level = _level;
		calcStats();
	}

	public long getExp() {
		return exp;
	}

	public void setExp(long exp) {
		this.exp = exp;
	}

	public LiveObject getOwner() {
		return owner;
	}

	public void setOwner(LiveObject owner) {
		this.owner = owner;
	}

	public long getExpForLevel(int level) {
		return Experience.LEVEL[level];
	}

	public boolean addExp(long value) {
		if ((getExp() + value) < 0
				|| getExp() == (getExpForLevel(Experience.MAX_LEVEL) - 1))
			return true;

		if (getExp() + value >= getExpForLevel(Experience.MAX_LEVEL))
			value = getExpForLevel(Experience.MAX_LEVEL) - 1 - getExp();

		setExp(getExp() + value);

		byte level = 0;
		for (level = 1; level <= Experience.MAX_LEVEL; level++) {
			if (getExp() >= getExpForLevel(level))
				continue;
			level--;
			break;
		}
		if (level != getLevel())
			addLevel((byte) (level - getLevel()));

		return true;
	}

	public boolean addLevel(byte value) {
		if (getLevel() + value > Experience.MAX_LEVEL - 1) {
			if (getLevel() < Experience.MAX_LEVEL - 1)
				value = (byte) (Experience.MAX_LEVEL - 1 - getLevel());
			else
				return false;
		}
		boolean levelIncreased = (getLevel() + value > getLevel());
		value += getLevel();
		setLevel(value);
		// Sync up exp with current level
		if (getExp() >= getExpForLevel(getLevel() + 1)
				|| getExpForLevel(getLevel()) > getExp())
			setExp(getExpForLevel(getLevel()));

		if (!levelIncreased)
			return false;

		return true;
	}

	public short getStr() {
		return str;
	}

	public void setStr(short str) {
		this.str = str;
		calcStats();
	}

	public short getDex() {
		return dex;
	}

	public void setDex(short dex) {
		this.dex = dex;
		calcStats();
	}

	public short getCon() {
		return con;
	}

	public void setCon(short con) {
		this.con = con;
		calcStats();
	}

	public float getAttackPower() {
		return attackPower;
	}

	public void setAttackPower(float attackPower) {
		this.attackPower = attackPower;
	}

	public float getAttackRate() {
		return attackRate;
	}

	public void setAttackRate(float attackSpeed) {
		this.attackRate = attackSpeed;
	}

	public float getCriticalPower() {
		return criticalPower;
	}

	public void setCriticalPower(float criticalPower) {
		this.criticalPower = criticalPower;
	}

	public float getCriticalChance() {
		return criticalChance;
	}

	public void setCriticalChance(float criticalChance) {
		this.criticalChance = criticalChance;
	}

	public int getBreath() {
		return currentBreath;
	}

	public void setBreath(int breath) {
		this.currentBreath = breath;
	}

	public int getWeight() {
		return weight;
	}

	public void setWeight(int weight) {
		this.weight = weight;
	}

	public int getCurrentHp() {
		return currentHp;
	}

	public void setCurrentHp(int currentHp) {
		if (currentHp >= getMaxHp()) {
			this.currentHp = getMaxHp();
			this.stopRegenerationTask();
		} else if (currentHp <= 0) {
			this.stopRegenerationTask();
			this.currentHp = 0;
		} else
			this.currentHp = currentHp;
	}

	public int getMaxHp() {
		return maxHp;
	}

	public void setMaxHp(int maxHp) {
		this.maxHp = maxHp;
	}

	public float getRegenHp() {
		return regenHp;
	}

	public void setRegenHp(float regenHp) {
		this.regenHp = regenHp;
	}

	public float getMoveSpeed() {
		return moveSpeed;
	}

	public void setMoveSpeed(float moveSpeed) {
		this.moveSpeed = moveSpeed;
	}

	public int getBaseHp() {
		return baseHp;
	}

	public void setBaseHp(int baseHp) {
		this.baseHp = baseHp;
	}

	public int getMaxBreath() {
		return maxBreath;
	}

	public void setMaxBreath(int maxBreath) {
		this.maxBreath = maxBreath;
	}

	public ArrayList<Modifier>[] getStatModifiers() {
		return statModifiers;
	}

	public float getBaseAttackRate() {
		return baseAttackRate;
	}

	public void setBaseAttackRate(float baseAttackSpeed) {
		this.baseAttackRate = baseAttackSpeed;
	}

	public float getBaseAttack() {
		return baseAttack;
	}

	public void setBaseAttack(float baseAttackPower) {
		this.baseAttack = baseAttackPower;
	}

	public float getBaseSpeed() {
		return baseSpeed;
	}

	public void setBaseSpeed(float baseSpeed) {
		this.baseSpeed = baseSpeed;
	}

	public float getBaseDefense() {
		return baseDefense;
	}

	public void setBaseDefense(float baseDefense) {
		this.baseDefense = baseDefense;
	}

	public float getDefenseRate() {
		return defenseRate;
	}

	public void setDefenseRate(float defenseRate) {
		this.defenseRate = defenseRate;
	}
	
	private void readObject(ObjectInputStream in) throws IOException,
	ClassNotFoundException {
		in.defaultReadObject();
		calcStats();
	}

}
